home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1992, 1993 NeXT Computer, Inc.
- *
- * Adaptec AHA-1542 SCSI controller driver.
- *
- * HISTORY
- *
- * 13 Apr 1993 Doug Mitchell at NeXT
- * Rewrote using I/O thread which has exclusive access to hardware.
- *
- * 22 Nov 1992 Brian Pinkerton at NeXT
- * Created from non-driverkit driver.
- */
-
- #import <sys/types.h>
- #import <bsd/sys/param.h>
- #import <objc/Object.h>
- #import <kernserv/queue.h>
- #import <kernserv/prototypes.h>
- #import <driverkit/return.h>
- #import <driverkit/generalFuncs.h>
- #import <driverkit/kernelDriver.h>
- #import <driverkit/i386/kernelDriver.h>
- #import <driverkit/interruptMsg.h>
- #import <driverkit/scsiTypes.h>
- #import <bsd/dev/scsireg.h>
- #import "scsivar.h"
- #import <mach/message.h>
- #import <mach/port.h>
- #import <mach/mach_interface.h>
- #import <machkit/NXLock.h>
- #import <kernserv/ns_timer.h>
- #import <driverkit/i386/ioPorts.h>
-
- #import <driverkit/i386/directDevice.h>
- #import <driverkit/i386/IOEISADeviceDescription.h>
- #import <driverkit/IOSCSIController.h>
- #import "AHAController.h"
- #import "AHATypes.h"
- #import "AHAInline.h"
- #import "AHAThread.h"
-
- extern unsigned ffs(unsigned mask);
-
- /*
- * Template for command message sent to the I/O thread.
- */
- static msg_header_t AHAMessageTemplate = {
- 0, // msg_unused
- 1, // msg_simple
- sizeof(msg_header_t), // msg_size
- MSG_TYPE_NORMAL, // msg_type
- PORT_NULL, // msg_local_port
- PORT_NULL, // msg_remote_port - TO
- // BE FILLED IN
- IO_COMMAND_MSG // msg_id
- };
-
- /*
- * Private methods implemented in this file.
- */
- @interface AHAController(PrivateMethods)
- - (BOOL) probeAtPortBase : (IOEISAPortAddress) portBase;
- - (IOReturn)executeCmdBuf : (AHACommandBuf *)cmdBuf;
- @end
-
-
- @implementation AHAController
-
- /*
- * Probe, configure board, and init new instance.
- */
- + (BOOL)probe:deviceDescription
- {
- AHAController *aha = [self alloc];
- IORange ioPort;
-
- ddm_init("AHAController probe\n", 1,2,3,4,5);
- aha->ioThreadRunning = NO;
-
- /*
- * Check that we have some IO Ports assigned, and probe using the
- * first IO Port.
- * -probeAtPortBase returns TRUE if there's an AHA Controller present.
- */
- if ([deviceDescription numPortRanges] < 1) {
- IOLog("AHAController: can't determine port base!\n");
- [aha free];
- return NO;
- }
- ioPort = [deviceDescription portRangeList][0];
- if (![aha probeAtPortBase:ioPort.start]) {
- IOLog("Adaptec154x Not Found at port 0x%x\n", ioPort.start);
- [aha free];
- return NO;
- }
- return ([aha initFromDeviceDescription:deviceDescription] ? YES : NO);
- }
-
- - initFromDeviceDescription:deviceDescription
- {
- unsigned Lun;
- kern_return_t krtn;
-
- ddm_init("AHAController initFromDeviceDescription\n", 1,2,3,4,5);
-
- queue_init(&outstandingQ);
- queue_init(&pendingQ);
- queue_init(&commandQ);
- commandLock = [[NXLock alloc] init];
- outstandingCount = 0;
- dmaLockCount = 0;
- numFreeCcbs = AHA_QUEUE_SIZE;
-
- /*
- * Note the I/O thread provided by IOSCSIController is running
- * upon return from the following method.
- */
- if ([super initFromDeviceDescription:deviceDescription] == nil)
- return [self free];
- interruptPortKern = IOConvertPort([self interruptPort],
- IO_KernelIOTask,
- IO_Kernel);
- ioThreadRunning = YES;
-
- /*
- * Check the channel and irq we just found against what's in our
- * device description. If they don't match, print a nasty warning
- * message and fail.
- */
- if ([deviceDescription numChannels] < 1 ||
- [deviceDescription channel] != config.dma_channel) {
- IOLog("AHAController: Actual DMA Channel (%d) doesn't match "
- "configured value (%d)!\n", config.dma_channel,
- ([deviceDescription numChannels] ?
- [deviceDescription channel] : 0));
- return [self free];
- }
-
- if ([deviceDescription numInterrupts] < 1 ||
- [deviceDescription interrupt] != config.irq) {
- IOLog("AHAController: Actual IRQ (%d) doesn't match "
- "configured value (%d)!\n", config.irq,
- ([deviceDescription numInterrupts] ?
- [deviceDescription interrupt] : 0));
- return [self free];
- }
-
- /*
- * Try to set up DMA. Set the appropriate mode on the channel, and
- * try to enable it.
- */
- if ([self setTransferMode:IO_Cascade forChannel:0] != IO_R_SUCCESS ||
- [self enableChannel:0] != IO_R_SUCCESS) {
- IOLog("AHAController: couldn't init DMA!\n");
- return [super free];
- }
-
- /*
- * Allocate Mailboxes and CCB's from low 16 M of memory.
- */
- ahaMbArea = IOMallocLow(sizeof(struct aha_mb_area));
- ahaCcb = IOMallocLow(sizeof(struct ccb) * AHA_QUEUE_SIZE);
-
- /*
- * Initialize driver data structures: set up the mailbox in/out area,
- * and initialize the CCB queues.
- *
- * Note that if we fail, the call to [super free] will release (and
- * disable) our resources (IRQ, DMA channel, portRanges).
- */
- if (!aha_setup_mb_area(ioBase, ahaMbArea, ahaCcb)) {
- IOLog("AHAController: couldn't set up mailbox area!\n");
- return [self free];
- }
-
- [self resetStats];
-
- /*
- * Reserve our target, enable interrupts, and go.
- */
- for(Lun=0; Lun<SCSI_NLUNS; Lun++) {
- [self reserveTarget:config.scsi_id lun:Lun forOwner:self];
- }
-
- [self enableAllInterrupts]; /* turn on interrupts */
-
- /*
- * Set the port queue length to the maximum size.
- */
- krtn = port_set_backlog(task_self(), [self interruptPort],
- PORT_BACKLOG_MAX);
- if(krtn) {
- IOLog("%s: error %d on port_set_backlog()\n",
- [self name], krtn);
- /* Oh well... */
- }
- [self resetSCSIBus];
- [self registerDevice]; /* this is the last thing we do! */
-
- return self;
- }
-
- /*
- * This is slightly incorrect, since we can actually handle more if some of
- * the entries in the scatter/gather list can be more than PAGE_SIZE. In
- * practice, tho, they're never bigger, so we'll make this our max size.
- * 18 Mar 93 dmitch - use (AHA_SG_COUNT - 1) since requests (the first
- * and the last) can cross page boundaries.
- */
- - (unsigned)maxTransfer
- {
- return (AHA_SG_COUNT - 1) * PAGE_SIZE;
- }
-
- /*
- * kill I/O thread, free up local dynamically allocated resources,
- * then have super release resources.
- */
- - free
- {
- AHACommandBuf cmdBuf;
-
- if(ioThreadRunning) {
- cmdBuf.op = AO_Abort;
- [self executeCmdBuf:&cmdBuf];
- }
- if(ahaMbArea) {
- IOFreeLow(ahaMbArea, sizeof(struct aha_mb_area));
- }
- if(ahaCcb) {
- IOFreeLow(ahaCcb, sizeof(struct ccb) * AHA_QUEUE_SIZE);
- }
- if(commandLock) {
- [commandLock free];
- }
- return [super free];
- }
-
- /*
- * Statistics support.
- */
- - (unsigned int) numQueueSamples
- {
- return totalCommands;
- }
-
-
- - (unsigned int) sumQueueLengths
- {
- return queueLenTotal;
- }
-
-
- - (unsigned int) maxQueueLength
- {
- return maxQueueLen;
- }
-
-
- - (void)resetStats
- {
- totalCommands = 0;
- queueLenTotal = 0;
- maxQueueLen = 0;
- }
-
- /*
- * Do a SCSI command, as specified by an IOSCSIRequest. All the
- * work is done by the I/O thread.
- */
- - (sc_status_t) executeRequest : (IOSCSIRequest *)scsiReq
- buffer : (void *)buffer
- client : (vm_task_t)client
- {
- AHACommandBuf cmdBuf;
-
- ddm_exp("executeRequest: cmdBuf 0x%x\n", &cmdBuf, 2,3,4,5);
-
- cmdBuf.op = AO_Execute;
- cmdBuf.scsiReq = scsiReq;
- cmdBuf.buffer = buffer;
- cmdBuf.client = client;
-
- [self executeCmdBuf:&cmdBuf];
-
- ddm_exp("executeRequest: cmdBuf 0x%x complete; result %d\n",
- &cmdBuf, cmdBuf.result, 3,4,5);
- return cmdBuf.result;
- }
-
-
- /*
- * Reset the SCSI bus. All the work is done by the I/O thread.
- */
- - (sc_status_t)resetSCSIBus
- {
- AHACommandBuf cmdBuf;
-
- ddm_exp("resetSCSIBus: cmdBuf 0x%x\n", &cmdBuf, 2,3,4,5);
-
- cmdBuf.op = AO_Reset;
- [self executeCmdBuf:&cmdBuf];
- return cmdBuf.result;
- }
- /*
- * The following 6 methods are all called from the I/O thread in
- * IODirectDevice.
- */
-
- /*
- * Called from the I/O thread when it receives an interrupt message.
- */
- - (void)interruptOccurred
- {
- struct ccb *ccb;
- aha_intr_reg_t intr;
- aha_mb_t *mb;
- int i;
-
- ddm_thr("interruptOccurred\n", 1,2,3,4,5);
-
- intr = aha_get_intr(ioBase);
- aha_clr_intr(ioBase);
-
- if (!intr.mb_in_full)
- return;
-
- /*
- * Find all ccb's which the controller has marked completed
- * and commandComplete: them.
- */
- mb = ahaMbArea->mb_in;
- for (i = 0; i < AHA_MB_CNT; i++, mb++) {
- if (mb->mb_stat != AHA_MB_IN_FREE) {
-
- /*
- * FIXME - need IOVirtualFromPhysical(); assume for
- * now that we can access all physical addresses.
- */
- ccb = (struct ccb *)aha_get_24(mb->ccb_addr);
- mb->mb_stat = AHA_MB_IN_FREE;
- queue_remove(&outstandingQ, ccb, struct ccb *, ccbQ);
- ASSERT(outstandingCount != 0);
- outstandingCount--;
-
- [self commandCompleted:ccb reason:CS_Complete];
- }
- }
-
- /*
- * Handle possible pending commands (now that we've dequeued at least
- * one CCB).
- */
- [self runPendingCommands];
-
- /*
- * One more thing - since we probably just freed up at least one
- * ccb, process possible entries waiting in commandQ.
- */
- [self commandRequestOccurred];
- ddm_thr("interruptOccurred: DONE\n", 1,2,3,4,5);
- }
-
- /*
- * These three should not occur; they are here as error traps. All three are
- * called out from the I/O thread upon receipt of messages which it should
- * not be seeing.
- */
- - (void)interruptOccurredAt:(int)localNum
- {
- IOLog("%s: interruptOccurredAt:%d\n", [self name], localNum);
- }
-
- - (void)otherOccurred:(int)id
- {
- IOLog("%s: otherOccurred:%d\n", [self name], id);
- }
-
- - (void)receiveMsg
- {
- IOLog("%s: receiveMsg\n", [self name]);
-
- /*
- * We have to let IODirectDevice take care of this (i.e., dequeue the
- * bogus message).
- */
- [super receiveMsg];
- }
-
- /*
- * Called from the I/O thread when it receives a timeout
- * message. We send these messages ourself from ahaTimeout() in
- * AHAThread.m.
- */
- - (void)timeoutOccurred
- {
- struct ccb *ccb, *nextCcb;
- ns_time_t now;
- queue_head_t *queue;
- BOOL ccbTimedOut = NO;
- AHACommandBuf *cmdBuf;
- IOSCSIRequest *scsiReq;
-
- ddm_thr("timeoutOccurred\n", 1,2,3,4,5);
-
- IOGetTimestamp(&now);
-
- /*
- * Scan the list of outstanding and pending commands, and time
- * out any ones whose time is past.
- */
-
- for (queue = &outstandingQ; queue != &pendingQ; queue = &pendingQ) {
-
- ccb = (struct ccb *) queue_first(&outstandingQ);
- while (!queue_end(&outstandingQ, (queue_entry_t) ccb)) {
- ns_time_t expire;
-
- cmdBuf = ccb->cmdBuf;
- scsiReq = cmdBuf->scsiReq;
- expire = ccb->startTime +
- 1000000000ULL *
- (unsigned long long)scsiReq->timeoutLength;
- if (now >= expire) {
- /*
- * Remove ccb from the oustanding queue and
- * complete it.
- */
- nextCcb = (struct ccb *) queue_next(&ccb->ccbQ);
- queue_remove(&outstandingQ, ccb, struct ccb *, ccbQ);
- if(queue == &outstandingQ) {
- ASSERT(outstandingCount != 0);
- outstandingCount--;
- }
- [self commandCompleted:ccb reason:CS_Timeout];
- ccb = nextCcb;
- ccbTimedOut = YES;
- }
- else {
- ccb = (struct ccb *) queue_next(&ccb->ccbQ);
- }
- }
- }
-
- /*
- * Reset bus. This also completes all I/Os in outstandingQ with
- * status CS_Reset.
- */
- if(ccbTimedOut) {
- [self threadResetBus:NULL];
- }
- ddm_thr("timeoutOccurred: DONE\n", 1,2,3,4,5);
- }
-
- /*
- * Process all commands in commandQ. If we run out of ccb's during this
- * method, we abort, leaving commands enqueued; these will be handled after
- * subqueuent interrupts.
- *
- * This is called either as a result of an IO_COMMAND_MSG message being
- * received by the I/O thread, or upon completion of interrupt handling. In
- * either case, it runs in the context of the I/O thread.
- */
- - (void)commandRequestOccurred
- {
- AHACommandBuf *cmdBuf;
-
- ddm_thr("commandRequestOccurred: top\n", 1,2,3,4,5);
- [commandLock lock];
- while(!queue_empty(&commandQ)) {
- cmdBuf = (AHACommandBuf *) queue_first(&commandQ);
- queue_remove(&commandQ, cmdBuf, AHACommandBuf *, link);
- [commandLock unlock];
- switch(cmdBuf->op) {
- case AO_Reset:
- [self threadResetBus:cmdBuf];
- break;
-
- case AO_Abort:
- /*
- * First notify caller of completion, then
- * self-terminate.
- */
- [cmdBuf->cmdLock lock];
- [cmdBuf->cmdLock unlockWith:CMD_COMPLETE];
- IOExitThread();
- /* not reached */
-
- case AO_Execute:
- if([self threadExecuteRequest:cmdBuf]) {
- /*
- * No more CCBs available. Abort this entire
- * method. Enqueue this request on the head
- * of commandQ for future processing.
- */
- [commandLock lock];
- queue_enter_first(&commandQ, cmdBuf,
- AHACommandBuf *, link);
- [commandLock unlock];
- ddm_thr("processCommandQ: no more ccbs; "
- "cmdBuf 0x%x\n", cmdBuf, 2,3,4,5);
- goto out;
-
- }
- }
- [commandLock lock];
- }
- [commandLock unlock];
- out:
- ddm_thr("commandRequestOccurred: DONE\n", 1,2,3,4,5);
- return;
- }
-
-
- @end /* methods declared in AHAController.h */
-
- @implementation AHAController(PrivateMethods)
-
- - (BOOL) probeAtPortBase:(IOEISAPortAddress) portBase
- {
- aha_inquiry_t inquiry;
-
- ddm_init("AHAController probeAtPortBase\n", 1,2,3,4,5);
-
- ioBase = portBase;
- aha_reset_board(ioBase, ahaBoardId);
-
- /*
- * Do an inquiry to find out the board id and other things that
- * we won't check.
- */
- if (!aha_probe_cmd(ioBase, AHA_CMD_DO_INQUIRY, 0, 0,
- (unsigned char *)&inquiry, sizeof(inquiry), TRUE)) {
- ddm_init(" ..inquiry command failed\n", 1,2,3,4,5);
- return FALSE;
- }
-
- ahaBoardId = inquiry.board_id;
-
- switch (ahaBoardId) {
-
- case AHA_1540_16HEAD:
- case AHA_154xB:
- case AHA_1540_64HEAD:
- case AHA_1640:
- case AHA_174xA:
- case AHA_154xC:
- break;
- default:
- if (ahaBoardId < AHA_154xC) {
- ddm_init("..bogus board ID (0x%x)\n", ahaBoardId,
- 2,3,4,5);
- return FALSE;
- }
- }
-
- /*
- * Attempt to read the configuration data from the board.
- * If this succeeds, then we have successfully probed.
- */
- if (!aha_probe_cmd(ioBase, AHA_CMD_GET_CONFIG, 0, 0,
- (unsigned char *)&config, sizeof(config), TRUE)) {
- ddm_init(" ..get config command failed\n", 1,2,3,4,5);
-
- return FALSE;
- }
-
- /*
- * Decode the values in the config struct.
- */
- config.irq = ffs((unsigned int) config.irq) + 8;
- config.dma_channel = ffs((unsigned int) config.dma_channel) - 1;
-
- IOLog("Adaptec154x at port 0x%x irq %d\n",
- portBase, config.irq);
- return TRUE;
- }
-
- /*
- * Pass one AHACommandBuf to the I/O thread; wait for completion.
- * Normal completion status is in cmdBuf->status; a non-zero return
- * from this function indicates a Mach IPC error.
- *
- * This method allocates and frees cmdBuf->cmdLock.
- */
- - (IOReturn)executeCmdBuf : (AHACommandBuf *)cmdBuf
- {
- msg_header_t msg = AHAMessageTemplate;
- kern_return_t krtn;
- IOReturn rtn = IO_R_SUCCESS;
-
- cmdBuf->cmdLock = [[NXConditionLock alloc] initWith:CMD_PENDING];
- [commandLock lock];
- queue_enter(&commandQ, cmdBuf, AHACommandBuf *, link);
- [commandLock unlock];
-
- /*
- * Create a Mach message and send it in order to wake up the
- * I/O thread.
- */
- msg.msg_remote_port = interruptPortKern;
- krtn = msg_send_from_kernel(&msg, MSG_OPTION_NONE, 0);
- if(krtn) {
- IOLog("%s: msg_send_from_kernel() returned %d\n",
- [self name], krtn);
- rtn = IO_R_IPC_FAILURE;
- goto out;
- }
-
- /*
- * Wait for I/O complete.
- */
- ddm_exp("executeCmdBuf: waiting for completion on cmdBuf 0x%x\n",
- cmdBuf, 2,3,4,5);
- [cmdBuf->cmdLock lockWhen:CMD_COMPLETE];
- ddm_exp("executeCmdBuf: cmdBuf 0x%x complete\n",
- cmdBuf, 2,3,4,5);
- out:
- [cmdBuf->cmdLock free];
- return rtn;
- }
-
- @end /* AHAController(PrivateMethods) */
-
-
-
-